#pragma once
#include "SehnsuchtRenderTargetBase.hxx"

#ifdef REIGN_DESKTOP // Desktop implementation
namespace Sehnsucht
{
	class DirectXBaseDesktop : public RenderTargetBase
	{
	protected:

		DirectXBaseDesktop();

		void CreateWindowSizeDependentResources();
		void UpdateForWindowSizeChange();

		void Initialize();
		void CreateDeviceIndependentResources();
		void CreateDeviceResources();
		void ValidateDevice();


		REIGN_HINTINLINING void HandleDeviceLost()
		{
			// Reset these member variables to ensure that SetDpi recreates all resources.
			float dpi = m_dpi; m_dpi = -1.0f;
			ZeroValue(&m_windowBounds);
			m_swapChain = nullptr;

			CreateDeviceResources();
			Dpi(dpi);
		}

	public:

		~DirectXBaseDesktop();

	public:


		REIGN_HINTINLINING void Driver(D3D_DRIVER_TYPE driver) { m_driver = driver; }
		REIGN_HINTINLINING D3D_DRIVER_TYPE Driver() { return m_driver; }
		REIGN_HINTINLINING void Window(HWND window) { m_window = window; }
		REIGN_HINTINLINING HWND Window() { return m_window; }

		REIGN_HINTINLINING void Dpi(float dpi)
		{
			if (dpi != m_dpi)
			{
				// Save the updated DPI value.
				m_dpi = dpi;

				// Update Direct2D's stored DPI.
				m_d2dContext->SetDpi(m_dpi, m_dpi);

				// Often a DPI change implies a window size change. In some cases Windows will issue
				// both a size changed event and a DPI changed event. In this case, the resulting bounds 
				// will not change, and the window resize code will only be executed once.
				UpdateForWindowSizeChange();
			}
		}

		REIGN_HINTINLINING float Dpi()
		{
			return m_dpi;
		}

		REIGN_FORCEINLINING void PresentVSync()
		{
			// The first argument instructs DXGI to block until VSync, putting the application
			// to sleep until the next VSync. This ensures we don't waste any cycles rendering
			// frames that will never be displayed to the screen.
			HRESULT hr = m_swapChain->Present(1, 0);

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

		REIGN_FORCEINLINING void PresentVSyncWithNullParams()
		{
			// The application may optionally specify "dirty" or "scroll"
			// rects to improve efficiency in certain scenarios.
			DXGI_PRESENT_PARAMETERS parameters = { 0 };
			parameters.DirtyRectsCount = 0;
			parameters.pDirtyRects = nullptr;
			parameters.pScrollRect = nullptr;
			parameters.pScrollOffset = nullptr;
			
			// The first argument instructs DXGI to block until VSync, putting the application
			// to sleep until the next VSync. This ensures we don't waste any cycles rendering
			// frames that will never be displayed to the screen.
			HRESULT hr = m_swapChain->Present1(1, 0, &parameters);

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

		REIGN_FORCEINLINING void PresentVSyncWithNullParamsAndDiscardViews()
		{
			// The application may optionally specify "dirty" or "scroll"
			// rects to improve efficiency in certain scenarios.
			DXGI_PRESENT_PARAMETERS parameters = { 0 };
			parameters.DirtyRectsCount = 0;
			parameters.pDirtyRects = nullptr;
			parameters.pScrollRect = nullptr;
			parameters.pScrollOffset = nullptr;

			// The first argument instructs DXGI to block until VSync, putting the application
			// to sleep until the next VSync. This ensures we don't waste any cycles rendering
			// frames that will never be displayed to the screen.
			HRESULT hr = m_swapChain->Present1(1, 0, &parameters);

			// Discard the contents of the render target.
			// This is a valid operation only when the existing contents will be entirely
			// overwritten. If dirty or scroll rects are used, this call should be removed.
			m_d3dContext->DiscardView(m_d3dRenderTargetView.Get());

			// Discard the contents of the depth stencil.
			m_d3dContext->DiscardView(m_d3dDepthStencilView.Get());

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

		REIGN_FORCEINLINING void Present()
		{
			// The first argument instructs DXGI to block until VSync, putting the application
			// to sleep until the next VSync. This ensures we don't waste any cycles rendering
			// frames that will never be displayed to the screen.
			HRESULT hr = m_swapChain->Present(0, 0);

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

		REIGN_FORCEINLINING void PresentWithNullParams()
		{
			// The application may optionally specify "dirty" or "scroll"
			// rects to improve efficiency in certain scenarios.
			DXGI_PRESENT_PARAMETERS parameters = { 0 };
			parameters.DirtyRectsCount = 0;
			parameters.pDirtyRects = nullptr;
			parameters.pScrollRect = nullptr;
			parameters.pScrollOffset = nullptr;

			// The first argument instructs DXGI to block until VSync, putting the application
			// to sleep until the next VSync. This ensures we don't waste any cycles rendering
			// frames that will never be displayed to the screen.
			HRESULT hr = m_swapChain->Present1(0, 0, &parameters);

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

		// The first argument instructs DXGI to block until VSync, putting the application
		// to sleep until the next VSync. This ensures we don't waste any cycles rendering
		// frames that will never be displayed to the screen. The second parameter specifies 
		// "dirty" or "scroll" rects to improve efficiency in certain scenarios.
		REIGN_FORCEINLINING void PresentWithParams(uint32 vsync, DXGI_PRESENT_PARAMETERS *parameters, bool discardViews)
		{
			HRESULT hr = m_swapChain->Present1(vsync, NoFlags, parameters); if (discardViews)
			{
				// Discard the contents of the render target.
				// This is a valid operation only when the existing contents will be entirely
				// overwritten. If dirty or scroll rects are used, this call should be removed.
				m_d3dContext->DiscardView(m_d3dRenderTargetView.Get());

				// Discard the contents of the depth stencil.
				m_d3dContext->DiscardView(m_d3dDepthStencilView.Get());
			}

			// If the device was removed either by a disconnect or a driver upgrade, we 
			// must recreate all device resources.
			if (hr == DXGI_ERROR_DEVICE_REMOVED)
			{
				HandleDeviceLost();
			}
			else
			{
				Sehnsucht::ThrowIfFailed(hr);
			}
		}

	public:

		inline float ConvertDipsToPixels(float dips)
		{
			static const float dipsPerInch = 96.0f;
			return floor(dips * m_dpi / dipsPerInch + 0.5f);
		}

	public:

#if _Reign_desktop_support_windows_7
		REIGN_FORCEINLINING IWICImagingFactory *WICImagingFactory() { return m_wicFactory.Get(); }
		REIGN_FORCEINLINING IDWriteFactory *DWriteFactory() { return m_dwriteFactory.Get(); }
		REIGN_FORCEINLINING ID3D11DeviceContext *Context3() { return m_d3dContext.Get(); }
		REIGN_FORCEINLINING ID3D11Device *Device3() { return m_d3dDevice.Get(); }
#else
		REIGN_FORCEINLINING IWICImagingFactory2 *WICImagingFactory() { return m_wicFactory.Get(); }
		REIGN_FORCEINLINING IDWriteFactory1 *DWriteFactory() { return m_dwriteFactory.Get(); }
		REIGN_FORCEINLINING ID3D11DeviceContext1 *Context3() { return m_d3dContext.Get(); }
		REIGN_FORCEINLINING ID3D11Device1 *Device3() { return m_d3dDevice.Get(); }
#endif

		REIGN_FORCEINLINING ID3D11RenderTargetView *RenderTargetView() { return m_d3dRenderTargetView.Get(); }
		REIGN_FORCEINLINING ID3D11DepthStencilView *DepthStencilView() { return m_d3dDepthStencilView.Get(); }
		REIGN_FORCEINLINING ID2D1Bitmap1 *TargetBitmap() { return m_d2dTargetBitmap.Get(); }
		REIGN_FORCEINLINING ID2D1DeviceContext *Context2() { return m_d2dContext.Get(); }
		REIGN_FORCEINLINING ID2D1Factory1 *D2D1Factory() { return m_d2dFactory.Get(); }
		REIGN_FORCEINLINING IDXGISwapChain1 *SwapChain() { return m_swapChain.Get(); }
		REIGN_FORCEINLINING ID2D1Device *Device2() { return m_d2dDevice.Get(); }

	protected:

		// DirectWrite & Windows Imaging Component Objects.

#if _Reign_desktop_support_windows_7
		Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;
		Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
#else
		Microsoft::WRL::ComPtr<IWICImagingFactory2> m_wicFactory;
		Microsoft::WRL::ComPtr<IDWriteFactory1> m_dwriteFactory;
#endif

		// DirectX Core Objects. Required for 2D and 3D.

#if _Reign_desktop_support_windows_7
		Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_d3dContext;
		Microsoft::WRL::ComPtr<ID3D11Device> m_d3dDevice;
#else
		Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_d3dContext;
		Microsoft::WRL::ComPtr<ID3D11Device1> m_d3dDevice;
#endif
		Microsoft::WRL::ComPtr<IDXGISwapChain1> m_swapChain;

		// Direct2D Rendering Objects. Required for 2D.

		Microsoft::WRL::ComPtr<ID2D1Factory1> m_d2dFactory;
		Microsoft::WRL::ComPtr<ID2D1Bitmap1> m_d2dTargetBitmap;
		Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice;
		Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dContext;

		// Transforms used for display orientation.

		DirectX::XMFLOAT4X4 m_orientationTransform3D;
		D2D1::Matrix3x2F m_orientationTransform2D;

		// Cached renderer properties.

		D3D_FEATURE_LEVEL m_d3dFeatureLevel;
		D2D1_SIZE_F m_d3dRenderTargetSize;
		D2D1_RECT_F m_windowBounds;
		D3D_DRIVER_TYPE m_driver;
		int16 m_orientation;
		float32 m_dpi;

		HWND m_window;

	};

};

#endif // All implementations